﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace DataInspector
{
    public class DataInspector : IDisposable
    {

        #region Properties

        public Stream Stream { protected set; get; }        
        public string Filename { private set; get; }

        #endregion
        
        #region Constructor

        public DataInspector(string filename)
        {
            this.Filename = filename;
            this.Stream = new FileStream(filename, FileMode.Open, FileAccess.ReadWrite);            
        }

        public DataInspector(Stream stream)
        {
            this.Filename = null;
            this.Stream = stream;
        }

        #endregion

        #region Methods
        
        protected void Write(Stream stream, string input, Encoding encoding)
        {
            int len = input.Length;

            byte[] data = new byte[len];

            data = encoding.GetBytes(input);

            stream.Write(data, 0, len);
        }

        public void Write(string input, Encoding encoding)
        {
            this.Write(this.Stream, input, encoding);
        }

        public void Insert(byte[] data, ProgressCallback progressCallback)
        {            
            if (this.Filename != null)
                throw new NotSupportedException("Operation not supported on filestream");
                
            long position = this.Stream.Position;

            long length = this.Stream.Length;

            MemoryStream ms = new MemoryStream();

            this.Stream.Position = 0;

            DIUtils.CopyStream(this.Stream, ms, position, progressCallback);

            ms.Write(data, 0, data.Length);

            this.Stream.Position = position;

            DIUtils.CopyStream(this.Stream, ms, this.Stream.Length - position, progressCallback);

            this.Stream = ms;
            
        }

        public void Write<T>(T arg) where T : struct
        {
            int len = System.Runtime.InteropServices.Marshal.SizeOf(arg);

            this.Write(arg, len);
        }

        public void Write<T>(T arg, int len) where T : struct
        {
            byte[] data = new byte[len];

            dynamic dinput = arg;

            data = BitConverter.GetBytes(dinput);

            this.Stream.Write(data, 0, len);
        }        

        public long Find(byte[] needle, ProgressCallback progressCallback)
        {
            long position = this.Stream.Position;

            FinderHelper fh = new FinderHelper();
            long index = fh.FindBytes(this.Stream, needle, progressCallback);

            this.Stream.Position = position;

            return index;
        }

        public byte[] Read(int len)
        {
            byte[] data = new byte[len];

            int lenDataRead = this.Stream.Read(data, 0, len);

            if (lenDataRead < len)            
                Array.Resize(ref data, lenDataRead);

            return data;
        }
        
        public T Read<T>() where T : struct, IConvertible
        {
            Type t = typeof(T);
            int len;
            //len = sizeof(t);
            len = System.Runtime.InteropServices.Marshal.SizeOf(t);
            byte[] data = new byte[len];

            int lenDataRead = this.Stream.Read(data, 0, len);

            if (lenDataRead < len)
                Array.Resize(ref data, lenDataRead);

            switch (Type.GetTypeCode(t))
            {
                case TypeCode.Byte:
                    return (T)Convert.ChangeType(data[0], typeof(T));
                case TypeCode.Int16:
                    return (T)Convert.ChangeType(BitConverter.ToInt16(data, 0), typeof(T));                    
                case TypeCode.Int32:
                    return (T)Convert.ChangeType(BitConverter.ToInt32(data, 0), typeof(T));
                case TypeCode.Int64:
                    return (T)Convert.ChangeType(BitConverter.ToInt64(data, 0), typeof(T));
                case TypeCode.Single:
                    return (T)Convert.ChangeType(BitConverter.ToSingle(data, 0), typeof(T));
                case TypeCode.Double:                    
                    return (T)Convert.ChangeType(BitConverter.ToDouble(data, 0), typeof(T));                
            }
            throw new NotSupportedException();     
        }
        
        public void Dispose()
        {
            this.Stream.Close();
        }

        #endregion

        #region FinderHelper

        /// <summary>
        /// FindHelper
        /// https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm
        /// </summary>
        class FinderHelper
        {
            const int BUFFER_SIZE = 32;

            int[] BuildShiftArray(byte[] byteSequence)
            {
                int[] shifts = new int[byteSequence.Length];
                int[] ret;
                int shiftCount = 0;
                byte end = byteSequence[byteSequence.Length - 1];
                int index = byteSequence.Length - 1;
                int shift = 1;

                while (--index >= 0)
                {
                    if (byteSequence[index] == end)
                    {
                        shifts[shiftCount++] = shift;
                        shift = 1;
                    }
                    else
                    {
                        shift++;
                    }
                }
                ret = new int[shiftCount];
                for (int i = 0; i < shiftCount; i++)
                {
                    ret[i] = shifts[i];
                }
                return ret;
            }

            byte[] FlushBuffer(byte[] buffer, int keepSize)
            {
                byte[] newBuffer = new byte[buffer.Length];
                for (int i = 0; i < keepSize; i++)
                {
                    newBuffer[i] = buffer[buffer.Length - keepSize + i];
                }
                return newBuffer;
            }

            int FindBytes(byte[] haystack, int haystackSize, byte[] needle, int[] shiftArray)
            {
                int index = needle.Length;
                int searchIndex, needleIndex, currentShiftIndex = 0, shift;
                bool shiftFlag = false;

                index = needle.Length;
                while (true)
                {
                    needleIndex = needle.Length - 1;
                    while (true)
                    {
                        if (index >= haystackSize)
                            return -1;
                        if (haystack[index] == needle[needleIndex])
                            break;
                        index++;
                    }
                    searchIndex = index;
                    needleIndex = needle.Length - 1;
                    while (needleIndex >= 0 && haystack[searchIndex] == needle[needleIndex])
                    {
                        searchIndex--;
                        needleIndex--;
                    }
                    if (needleIndex < 0)
                        return index - needle.Length + 1;
                    if (shiftFlag)
                    {
                        shiftFlag = false;
                        index += shiftArray[0];
                        currentShiftIndex = 1;
                    }
                    else if (currentShiftIndex >= shiftArray.Length)
                    {
                        shiftFlag = true;
                        index++;
                    }
                    else
                    {
                        index += shiftArray[currentShiftIndex++];
                    }
                }
            }


            public long FindBytes(Stream stream, byte[] needle, ProgressCallback progressCallback)
            {
                byte[] buffer = new byte[BUFFER_SIZE];
                int[] shiftArray = BuildShiftArray(needle);
                int bufferSize, initBufferSize;
                long offset = 0;
                int init = needle.Length;
                int val;

                try
                {
                    while (true)
                    {
                        bufferSize = stream.Read(buffer, needle.Length - init, buffer.Length - needle.Length + init);
                        if (bufferSize <= 0)
                            return -1;
                        if ((val = FindBytes(buffer, bufferSize + needle.Length - init, needle, shiftArray)) != -1)
                            return val + offset;
                        buffer = FlushBuffer(buffer, needle.Length);
                        offset += bufferSize - init;
                        init = 0;
                        if (progressCallback != null)
                            progressCallback(stream.Position, stream.Length);
                    }
                }
                catch (IOException e)
                {
                    //throw e;
                }
                return -1;
            }
        } 
        #endregion

        #region Delegates

        public delegate void ProgressCallback(long position, long total);

        #endregion

    }
}
